package cz.semjob.output;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

import cz.semjob.document.annotation.LabeledRelationWordsPair;
import cz.semjob.document.annotation.LabeledWords;
import cz.semjob.document.annotation.metadata.AnnotationTypeFactory;
import cz.semjob.document.annotation.metadata.Entity;
import cz.semjob.document.annotation.metadata.Relation;
import cz.semjob.document.processing.DocumentWord;
import cz.semjob.document.util.DocumentUtil;
import cz.semjob.framework.DocumentAnnotationResults;
import cz.semjob.output.data.CandidateConcept;
import cz.semjob.output.data.CandidateInstance;
import cz.semjob.output.data.ClassificationInfo;
import cz.semjob.output.data.EnrichmentOutput;

@Component
public class EnrichmentOutputProcessor {

	private class LabeledWordsHelper {
		private String lemma;
		private Entity entity;

		public LabeledWordsHelper(Entity entity, String lemma) {
			super();
			// this.labeledWordsList.add(labeledWords);
			this.lemma = lemma;
			this.entity = entity;

		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + getOuterType().hashCode();
			result = prime * result
					+ ((entity == null) ? 0 : entity.hashCode());
			result = prime * result + ((lemma == null) ? 0 : lemma.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			LabeledWordsHelper other = (LabeledWordsHelper) obj;
			if (!getOuterType().equals(other.getOuterType()))
				return false;
			if (entity == null) {
				if (other.entity != null)
					return false;
			} else if (!entity.equals(other.entity))
				return false;
			if (lemma == null) {
				if (other.lemma != null)
					return false;
			} else if (!lemma.equals(other.lemma))
				return false;
			return true;
		}

		private EnrichmentOutputProcessor getOuterType() {
			return EnrichmentOutputProcessor.this;
		}

	}

	private class LabeledRelationWordsPairHelper {
		private Relation relation;
		private CandidateConcept candidateConcept1;
		private CandidateConcept candidateConcept2;
		
		private List<DocumentWord> words1;
		private List<DocumentWord> words2;

		public LabeledRelationWordsPairHelper(Relation relation,
				CandidateConcept candidateConcept1,
				CandidateConcept candidateConcept2, List<DocumentWord> words1,
				List<DocumentWord> words2) {
			super();
			this.relation = relation;
			this.candidateConcept1 = candidateConcept1;
			this.candidateConcept2 = candidateConcept2;
			this.words1 = words1;
			this.words2 = words2;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + getOuterType().hashCode();
			result = prime
					* result
					+ ((candidateConcept1 == null) ? 0 : candidateConcept1
							.hashCode());
			result = prime
					* result
					+ ((candidateConcept2 == null) ? 0 : candidateConcept2
							.hashCode());
			result = prime * result
					+ ((relation == null) ? 0 : relation.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			LabeledRelationWordsPairHelper other = (LabeledRelationWordsPairHelper) obj;
			if (!getOuterType().equals(other.getOuterType()))
				return false;
			if (candidateConcept1 == null) {
				if (other.candidateConcept1 != null)
					return false;
			} else if (!candidateConcept1.equals(other.candidateConcept1))
				return false;
			if (candidateConcept2 == null) {
				if (other.candidateConcept2 != null)
					return false;
			} else if (!candidateConcept2.equals(other.candidateConcept2))
				return false;
			if (relation == null) {
				if (other.relation != null)
					return false;
			} else if (!relation.equals(other.relation))
				return false;
			return true;
		}

		private EnrichmentOutputProcessor getOuterType() {
			return EnrichmentOutputProcessor.this;
		}

	}

	private static Logger logger = Logger.getLogger(EnrichmentOutputter.class); 

	public EnrichmentOutputProcessor() {

	}

	public EnrichmentOutput processOutput(
			List<DocumentAnnotationResults> documentAnnotationResultsList) {

		EnrichmentOutput enrichmentOutput = new EnrichmentOutput();

		Map<List<DocumentWord>, CandidateConcept> wordsConceptMap = new HashMap<List<DocumentWord>, CandidateConcept>();
		// count the merged concept occurences
		Map<LabeledWordsHelper, Integer> conceptOccurencesCount = new HashMap<LabeledWordsHelper, Integer>();
		Map<LabeledWordsHelper, List<LabeledWords>> conceptWordsMapping = new HashMap<LabeledWordsHelper, List<LabeledWords>>();
		int i=0;
		for (DocumentAnnotationResults documentAnnotationResults : documentAnnotationResultsList) {
			for (LabeledWords labeledWords : documentAnnotationResults
					.getLabeledWordsWithDuplicates()) {
				if (documentAnnotationResults.getLabeledWords().contains(labeledWords)) {
					updateConceptOccurencesCount(conceptOccurencesCount, conceptWordsMapping,
						labeledWords, false);
				} else {
					updateConceptOccurencesCount(conceptOccurencesCount, conceptWordsMapping,
							labeledWords, true);
				}
				i++;
			}
		}
		Map<String, ClassificationInfo> classificationInfoMap = documentAnnotationResultsList.get(0).getClassifierInfoMap();
		System.out.println("Total labeled words count: " + i);
		System.out.println("Extraced concepts count: " + conceptOccurencesCount.keySet().size() );

		i=0;
		for (LabeledWordsHelper concept : conceptOccurencesCount.keySet()) {
			List<LabeledWords> conceptWordsList = conceptWordsMapping.get(concept);
			CandidateConcept candidateConcept = new CandidateConcept(
					conceptWordsList,
					conceptOccurencesCount.get(concept), concept.lemma, concept.entity, classificationInfoMap.get(concept.entity.getName()));
			enrichmentOutput.addCandidateConcept(candidateConcept);
			for (LabeledWords conceptWords : conceptWordsList) {
				// we create concept words mapping to get proper concept in
				// relations creating
				wordsConceptMap.put(conceptWords.getWords(), candidateConcept);
				i++;
			}
		}
		System.out.println("Words in concepts: " + i);
		
		// now we process relations
		// first count occurences
		Map<LabeledRelationWordsPairHelper, Integer> relationsOccurencesCount = new HashMap<LabeledRelationWordsPairHelper, Integer>();
		for (DocumentAnnotationResults documentAnnotationResults : documentAnnotationResultsList) {
			for (LabeledRelationWordsPair relation : documentAnnotationResults.getLabeledRelations()) {
				updateRelationOccurencesCount(relationsOccurencesCount, wordsConceptMap,
						relation);
			}
		}
		for(LabeledRelationWordsPairHelper relationHelper : relationsOccurencesCount.keySet()) {
			CandidateConcept  concept1 = relationHelper.candidateConcept1;
			CandidateConcept  concept2 = relationHelper.candidateConcept2;
			Relation relation = relationHelper.relation;
			if (relation.getName().equals("is-a")) { // TODO dat do application properties
				concept1.addParentConcept(concept2);
				concept2.addSubConcept(concept1);
			} else if (relation.getName().equals("cohypo")) {
				if (!concept1.getParentConcepts().isEmpty() && concept2.getParentConcepts().isEmpty()) {
					concept2.addParentConcepts(concept1.getParentConcepts(concept2.getEntity()));
				}
				if (concept1.getParentConcepts().isEmpty() && !concept2.getParentConcepts().isEmpty()) {
					concept1.addParentConcepts(concept2.getParentConcepts(concept1.getEntity()));
				}
			}
		}
		// now create instances
		Map<List<DocumentWord>, CandidateInstance> instanceWordsMap = new HashMap<List<DocumentWord>, CandidateInstance>();
		for (CandidateConcept candidateConcept : enrichmentOutput.getCandidateConcepts()) {
			for (LabeledWords conceptOccurenceWords : candidateConcept.getWords()) {
				CandidateInstance candidateInstance = new CandidateInstance(conceptOccurenceWords.getDocumentInstance(), candidateConcept, conceptOccurenceWords.getStartIndex(), conceptOccurenceWords.getEndIndex(), conceptOccurenceWords.getText());
				enrichmentOutput.addCandidateInstance(candidateInstance);
				instanceWordsMap.put(conceptOccurenceWords.getWords(), candidateInstance);
			}
		}
		for(LabeledRelationWordsPairHelper relationHelper : relationsOccurencesCount.keySet()) {
			Relation relation = relationHelper.relation;
			if (relation.getType().equals(AnnotationTypeFactory.nontaxonomicRelationType)) {
				List<DocumentWord> words1 = relationHelper.words1;
				List<DocumentWord> words2 = relationHelper.words2;
				CandidateInstance instance1 = instanceWordsMap.get(words1);
				CandidateInstance instance2 = instanceWordsMap.get(words2);
				instance1.addRelation(relation.getName(), instance2);
			}
		}
		return enrichmentOutput;
	}

	private void updateRelationOccurencesCount(
			Map<LabeledRelationWordsPairHelper, Integer> relationsOccurencesCount,
			Map<List<DocumentWord>, CandidateConcept> wordsConceptMap,
			LabeledRelationWordsPair relation) {
		CandidateConcept concept1 = wordsConceptMap.get(relation
				.getRelationWord1());
		CandidateConcept concept2 = wordsConceptMap.get(relation
				.getRelationWord2());
		if (concept1 == null || concept2 == null) {
			throw new IllegalArgumentException(
					"One of relation concepts is null");
		}
		LabeledRelationWordsPairHelper relationHelper = new LabeledRelationWordsPairHelper(
				relation.getRelation(), concept1, concept2, relation.getRelationWord1(), relation.getRelationWord2());
		if (relationsOccurencesCount.containsKey(relationHelper)) {
			relationsOccurencesCount.put(relationHelper,
					relationsOccurencesCount.get(relationHelper) + 1);
		} else {
			relationsOccurencesCount.put(relationHelper, 1);
		}

	}

	private void updateConceptOccurencesCount(
			Map<LabeledWordsHelper, Integer> conceptOccurencesCount,
			Map<LabeledWordsHelper, List<LabeledWords>> conceptWordsMapping,
			LabeledWords labeledWords, boolean duplicate) { // we do not count
															// occurences for
															// duplicates
		LabeledWordsHelper labeledWordsHelper = new LabeledWordsHelper(
				labeledWords.getLabel(),
				DocumentUtil.printLemmaWords(labeledWords.getWords()));
		// we create new helper to check if lemma and label already exists in
		// the map

		if (conceptOccurencesCount.containsKey(labeledWordsHelper)
				&& !duplicate) {
			conceptOccurencesCount.put(labeledWordsHelper,
					conceptOccurencesCount.get(labeledWordsHelper) + 1);
		} else if (!conceptOccurencesCount.containsKey(labeledWordsHelper)) {

			conceptOccurencesCount.put(labeledWordsHelper, 1);
			conceptWordsMapping.put(labeledWordsHelper,
					new ArrayList<LabeledWords>());
		}
		conceptWordsMapping.get(labeledWordsHelper).add(labeledWords);
	}

}
